package com.ydlclass;

import javax.xml.crypto.Data;
import java.util.concurrent.atomic.LongAdder;

public class IdGenerator {
    // 这是单机版本的线程安全的id发号器，一旦变成集群，就不行了

//    private static LongAdder longAdder = new LongAdder();
//
//    public static long getId(){
//        longAdder.increment();
//        return longAdder.sum();
//    }

    // 雪花算法 ---- 世界上没有任何两片雪花是一样的
    // 机房号(数据中心) 5bit  32个最多
    // 机器号 5bit 32
    // 时间戳(long 1970-1-1开始) 原本64位表示时间，减少到42位，所以我们选择更接近的日期作为我们的起始日期
    // 其次，同一个机房的同一个机器号的同一时间，可能有很大的并发任务，需要多个id
    // 所以还需要一个序列号 12bit
    // 一共 5+5+42+12 = 64

    //起始时间戳
    public static final long START_STAMP = DateUtil.get("2022-1-1").getTime();
    //
    public static final long DATA_CENTER_BIT = 5L;
    public static final long MACHINE_BIT = 5L;
    public static final long SEQUENCE_BIT = 12L;

    //最大值
    public static final long DATA_CENTER_MAX = ~(-1L << DATA_CENTER_BIT);
    public static final long MACHINE_BIT_MAX = ~(-1L << MACHINE_BIT);

    public static final long SEQUENCE_MAX = ~(-1L << SEQUENCE_BIT);

    //时间戳(42) 机房号(5) 机器号(5) 序列号(12)
    public static final long TIMESTAMP_LEFT = DATA_CENTER_BIT + MACHINE_BIT + SEQUENCE_BIT;
    public static final long DATA_CENTER_LEFT = MACHINE_BIT + SEQUENCE_BIT;
    public static final long MACHINE_LEFT = SEQUENCE_BIT;

    private long dataCenterId;
    private long machineId;
    private LongAdder sequenceId = new LongAdder();
    //时钟回拨的问题，需要去解决
    private long lastTimeStamp = -1L;

    public IdGenerator(long dataCenterId, long machineId) {
        // 判断传入的参数是否合法
        if(dataCenterId > DATA_CENTER_MAX || machineId > MACHINE_BIT_MAX){
            throw new IllegalArgumentException("你传入的数据中心编号或机器号不合法");
        }
        this.dataCenterId = dataCenterId;
        this.machineId = machineId;
    }

    public long getId(){
        // 第一步，处理时间戳问题
        long currentTime = System.currentTimeMillis();
        long timeStamp = currentTime - START_STAMP;

        // 判断时钟回拨
        if(timeStamp < lastTimeStamp){
            throw new RuntimeException("你的服务器进行了时钟回调.");
        }

        // sequenceId 需要做一些处理，如果是同一个时间节点，必须自增
        if(timeStamp == lastTimeStamp){
            sequenceId.increment();

            // 在同一时刻，超过了序列极限
            if(sequenceId.sum() == SEQUENCE_MAX){
                timeStamp = getNextTimeStamp();
                sequenceId.reset();
            }
        }else{
            sequenceId.reset();
        }
        // 执行结束之后将时间戳赋值给lastTimeStamp
        lastTimeStamp = timeStamp;
        long sequence = sequenceId.sum();

        return timeStamp << TIMESTAMP_LEFT | dataCenterId << DATA_CENTER_LEFT | machineId << MACHINE_LEFT | sequence;
    }

    private long getNextTimeStamp(){
        // 获取当前的时间戳
        long current = System.currentTimeMillis() - START_STAMP;
        // 如果一样就一直循环 直到下一个时间戳。
        while(current == lastTimeStamp){

            current = System.currentTimeMillis() - START_STAMP;
        }
        return current;
    }

}
